#include "../inc/sched.h"

#include <linux/sched.h>
#include <uapi/linux/sched/types.h>

static inline int __normal_prio(int policy, int rt_prio, int nice)
{
    int prio;

    if (dl_policy(policy))
        prio = MAX_DL_PRIO - 1;
    else if (rt_policy(policy))
        prio = MAX_RT_PRIO - 1 - rt_prio;
    else
        prio = NICE_TO_PRIO(nice);

    return prio;
}

/*
 * Calculate the expected normal priority: i.e. priority
 * without taking RT-inheritance into account. Might be
 * boosted by interactivity modifiers. Changes upon fork,
 * setprio syscalls, and whenever the interactivity
 * estimator recalculates.
 */
static inline int normal_prio(struct task_struct *p)
{
	return __normal_prio(p->policy, p->rt_priority, PRIO_TO_NICE(p->static_prio));
}

/*
 * sched_setparam() passes in -1 for its policy, to let the functions
 * it calls know not to change it.
 */
#define SETPARAM_POLICY	-1

static void __setscheduler_params(struct task_struct *p,
		const struct sched_attr *attr)
{
	int policy = attr->sched_policy;

	if (policy == SETPARAM_POLICY)
		policy = p->policy;

	p->policy = policy;

	/*
	 * __sched_setscheduler() ensures attr->sched_priority == 0 when
	 * !rt_policy. Always setting this ensures that things like
	 * getparam()/getattr() don't report silly values for !rt tasks.
	 */
	p->rt_priority = attr->sched_priority;
	p->normal_prio = normal_prio(p);
}

int __sched_setscheduler(struct task_struct *p,
                         const struct sched_attr *attr,
                         bool user, bool pi)
{
    int oldpolicy = -1, policy = attr->sched_policy;
    struct rq *rq;
    struct rq_flags rf;
    const struct sched_class *prev_class, *next_class;
    int newprio, oldprio;
    int queued, running;
    int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;

    if (policy < 0)
    {
        policy = oldpolicy = p->policy;
    }
    else
    {
        if (!valid_policy(policy))
            return -EINVAL;
    }

    /*
     * Make sure no PI-waiters arrive (or leave) while we are
     * changing the priority of the task:
     *
     * To be able to change p->policy safely, the appropriate
     * runqueue lock must be held.
     */
    rq = task_rq_lock(p, &rf);
    update_rq_clock(rq);

    oldprio = p->prio;

    newprio = __normal_prio(policy, attr->sched_priority, attr->sched_nice);

    prev_class = p->sched_class;
    next_class = __setscheduler_class(policy, newprio);

    if (prev_class != next_class && p->se.sched_delayed)
        dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);

    queued = task_on_rq_queued(p);
    running = task_current_donor(rq, p);
    if (queued)
        dequeue_task(rq, p, queue_flags);
    if (running)
        put_prev_task(rq, p);

    __setscheduler_params(p, attr);
    p->sched_class = next_class;
    p->prio = newprio;

    check_class_changing(rq, p, prev_class);

    if (queued)
    {
        /*
         * We enqueue to tail when the priority of a task is
         * increased (user space view).
         */
        if (oldprio < p->prio)
            queue_flags |= ENQUEUE_HEAD;

        enqueue_task(rq, p, queue_flags);
    }
    if (running)
        set_next_task(rq, p);

    check_class_changed(rq, p, prev_class, oldprio);

    task_rq_unlock(rq, p, &rf);

    return 0;
}

static int _sched_setscheduler(struct task_struct *p, int policy,
                               const struct sched_param *param, bool check)
{
    struct sched_attr attr = {
        .sched_policy = policy,
        .sched_priority = param->sched_priority,
        .sched_nice = PRIO_TO_NICE(p->static_prio),
    };

    return __sched_setscheduler(p, &attr, check, true);
}

/**
 * sched_setscheduler_nocheck - change the scheduling policy and/or RT priority of a thread from kernel-space.
 * @p: the task in question.
 * @policy: new policy.
 * @param: structure containing the new RT priority.
 *
 * Just like sched_setscheduler, only don't bother checking if the
 * current context has permission.  For example, this is needed in
 * stop_machine(): we create temporary high priority worker threads,
 * but our caller might not have that capability.
 *
 * Return: 0 on success. An error code otherwise.
 */
int sched_setscheduler_nocheck(struct task_struct *p, int policy,
                               const struct sched_param *param)
{
    return _sched_setscheduler(p, policy, param, false);
}
